﻿using System;
using System.Collections.Generic;

namespace MockingBird.Simulator.Framework
{
    using System.IO;
    using System.Xml;
    using Common.Infrastructure.Logging;
    using Configuration;
    using Interfaces;

    /// <summary>
    /// Process Soap headers config and return soap headers xml where applicable
    /// </summary>
    internal class SoapHeaderBuilder
    {
        private IExecutionContext context;
        private ILogger logger;
        private RequestSoapHeadersType headersConfig;
        private ApplicationConfiguration appConfig;
        private XmlDocument request;

        public SoapHeaderBuilder(IExecutionContext context, ILogger logger, ApplicationConfiguration appConfig)
        {
            this.context = context;
            this.logger = logger;
            this.appConfig = appConfig;
        }

        public SoapHeaderBuilder(IExecutionContext context, ILogger logger, ApplicationConfiguration appConfig, XmlDocument request)
        {
            this.context = context;
            this.logger = logger;
            this.appConfig = appConfig;
            this.request = request;
        }

        /// <summary>
        /// Returns SOAP headers based on the configuration
        /// </summary>
        /// <param name="headersConfig">Config describing header setting from the instance / app config</param>
        /// <returns>Xml doc containing headers or null</returns>
        public XmlDocument ResponseSoapHeaders(RequestSoapHeadersType headersConfig)
        {
            if (!Validate(headersConfig))
            {
                return null;
            }
            else
            {
                this.headersConfig = headersConfig;
            }

            XmlDocument headers = null;
            string filePath;

            // Two options exist in the config, pick up response headers from file
            // or build from the supplied configuration
            if (RespondWithHeadersFromFile(out filePath))
            {
                headers = GetHeadersFromFile(filePath);
            }
            else
            {
                // may return null if no config
                headers = BuildHeadersFromConfig();
            }

            return headers;
        }

        #region private

        private bool Validate(RequestSoapHeadersType headersConfig)
        {
            if (null == headersConfig)
            {
                // nothing to do (takes responsibility for checking whether there's anything to do)
                logger.LogInfo("SoapHeadersProcessing: Soap headers not found, nothing to do");
                return false;
            }

            return true;
        }

        private XmlDocument GetHeadersFromFile(string responseHeadersFilePath)
        {
            logger.LogInfo("SoapHeadersProcessing: Soap headers config found, getting response headers from file");

            XmlDocument headers = new XmlDocument();

            try
            {
                headers.Load(responseHeadersFilePath);
            }
            catch (Exception e)
            {
                throw new System.IO.FileLoadException("Soap headers processing: Response headers file could not be opened/read", e);
            }

            return headers;
        }

        private XmlDocument BuildHeadersFromConfig()
        {
            logger.LogInfo("SoapHeadersProcessing: Soap headers config found, building response headers from config");

            if (null == this.headersConfig.EchoRequestHeaders || this.headersConfig.EchoRequestHeaders.Count == 0)
            {
                return null;
            }
            return ProcessRequestHeadersConfig(this.headersConfig.EchoRequestHeaders);
        }

        /// <summary>
        /// Build a set of response headers from configuration
        /// </summary>
        /// <param name="requestHeaderConfigList"></param>
        /// <returns></returns>
        private XmlDocument ProcessRequestHeadersConfig(List<RequestHeaderType> requestHeaderConfigList)
        {
            XmlDocument responseHeader = new XmlDocument();
            responseHeader.LoadXml("<Header/>");

            foreach (RequestHeaderType requestHeaderConfig in requestHeaderConfigList)
            {
                XmlElement xmlTemplate = null;
                bool useXpaths = false;

                if (ValidateRequestHeaderConfig(requestHeaderConfig, out xmlTemplate, out useXpaths))
                {
                    if (useXpaths)
                    {
                        xmlTemplate = ProcessXpaths(requestHeaderConfig.Xpaths, xmlTemplate.OuterXml, requestHeaderConfig.RequestHeaderName);
                    }
                }

                if (null != xmlTemplate)
                {
                    // add node
                    responseHeader.DocumentElement.AppendChild(responseHeader.ImportNode(xmlTemplate, true));
                }
            }

            // check if anything was done
            if (responseHeader.DocumentElement.ChildNodes.Count == 0)
            {
                responseHeader = null;
            }

            return responseHeader;
        }

        private XmlElement ProcessXpaths(List<string> xpaths, string xml, string requestHeaderName)
        {
            List<string> values = new List<string>();
            foreach (string xpath in xpaths)
            {
                string value = GetValueFromRequest(xpath, requestHeaderName);
                if (null != value)
                {
                    values.Add(value);
                }
            }
            if (values.Count > 0)
            {
                // use string format to rebuild xml with values
                xml = string.Format(xml, values.ToArray());

                XmlDocument template = new XmlDocument();
                template.LoadXml(xml);
                return template.DocumentElement;
            }
            else
            {
                return null;
            }
        }

        private string GetValueFromRequest(string xpath, string requestHeaderName)
        {
            if (null == this.request)
            {
                throw new InvalidOperationException("Expected request");
            }

            XmlNode requestHeader = this.request.DocumentElement.SelectSingleNode("*[local-name()='Header']");

            string value = string.Empty;

            if (null == requestHeader)
            {
                return null;
            }
            else
            {
                value = requestHeader.SelectSingleNode(xpath).InnerText;
            }

            return value;
        }

        private bool ValidateRequestHeaderConfig(RequestHeaderType requestHeaderConfig, out XmlElement responseHeaderXmlTemplate, out bool useXpaths)
        {
            responseHeaderXmlTemplate = null;
            useXpaths = false;

            if (null == requestHeaderConfig)
            {
                return false;
            }
            if (string.IsNullOrEmpty(requestHeaderConfig.RequestHeaderName))
            {
                return false;
            }

            if (null != requestHeaderConfig.Xpaths && requestHeaderConfig.Xpaths.Count > 0)
            {
                // if a request header is defined and can't be matched it's OK provided there are no xpaths to work on
                if (!RequestHeaderExistsInRequest(requestHeaderConfig.RequestHeaderName))
                {
                    return false;
                }
                useXpaths = true;
            }

            if (null == requestHeaderConfig.ResponseHeaderXml)
            {
                return false;
            }

            responseHeaderXmlTemplate = requestHeaderConfig.ResponseHeaderXml;

            return true;
        }

        private bool RequestHeaderExistsInRequest(string requestHeaderToMatch)
        {
            // find request header
            string xpath = System.String.Format("/*[local-name()='Envelope']/*[local-name()='Header']/*[local-name()='{0}']", requestHeaderToMatch);

            if (null == this.request)
            {
                throw new InvalidOperationException("Expected request");
            }

            XmlNodeList nl = this.request.SelectNodes(xpath);

            if (null == nl)
            {
                return false;
            }
            else
            {
                return (nl.Count > 0);
            }
        }

        private bool RespondWithHeadersFromFile(out string filePath)
        {
            filePath = string.Empty;

            if (!string.IsNullOrEmpty(headersConfig.SetResponseHeaderFromThisFilePath))
            {
                //Find the response Root for the response file
                string responseRoot = appConfig.EndpointsRoot + @"\" + context.EndpointName + @"\" + "responses";
                //Update the responseFileName to be a complete path
                filePath = Path.Combine(responseRoot, headersConfig.SetResponseHeaderFromThisFilePath);

                if (File.Exists(filePath))
                {
                    return true;
                }
                else
                {
                    throw new InvalidOperationException("Cannot locate response file; " + filePath);
                }
            }
            return false;
        }


        #endregion




    }
}
